package br.com.puertorico.mecanismos.ui.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.BeanUtils;

import br.com.puertorico.common.exceptions.SystemException;
import br.com.puertorico.common.loggin.Logger;

/**
 * Esta classe cont&eacute;m um conjunto de m&eacute;todo amplamente utilizados
 * para manipular componentes do JavaServer Faces.
 * 
 * @author <a href="mailto:danielsudpr@gmail.com">Daniel Camargo</a>
 * @version 1.0
 */
@SuppressWarnings("rawtypes")
public class FacesUtils {

	/** O campo constante log. */
	private static final Logger log = Logger.getLogger(FacesUtils.class);

	/**
	 * Retorna o contexto do servlet.
	 * 
	 * @return O contexto do servlet
	 */
	public static ServletContext getServletContext() {
		return (ServletContext) getFacesContext().getExternalContext()
				.getContext();
	}

	/**
	 * Retorna a request do contexto externo do faces.
	 * 
	 * @return Request do contexto externo do faces.
	 */
	public static HttpServletRequest getRequestScope() {
		return (HttpServletRequest) getFacesContext().getExternalContext()
				.getRequest();
	}

	/**
	 * Retorna o response do contexto externo do faces.
	 * 
	 * @return Request do contexto externo do faces.
	 */
	public static HttpServletResponse getResponseScope() {
		return (HttpServletResponse) getFacesContext().getExternalContext()
				.getResponse();
	}

	/**
	 * Retorna os parametros da requisicao da URL.
	 * 
	 * @return Map com par de chaves e valores da requisicao.
	 */
	public static Map<String, String> getRequestParam() {
		return getFacesContext().getExternalContext().getRequestParameterMap();
	}

	/**
	 * Retorna a sess&otilde;o do contexto externo do faces.
	 * 
	 * @return HttpSession do contexto externo do faces.
	 */
	public static HttpSession getSessionScope() {
		return getRequestScope().getSession();
	}

	/**
	 * M&ecute;todo de recupera&ccedil;&atilde;o do campo faces context.
	 * 
	 * @return o campo faces context
	 */
	public static FacesContext getFacesContext() {
		return FacesContext.getCurrentInstance();
	}

	/**
	 * M&ecute;todo de recupera&ccedil;&atilde;o do campo view root.
	 * 
	 * @return o campo view root
	 */
	public static String getViewRoot() {
		return getFacesContext().getViewRoot().getViewId();
	}

	/**
	 * Recupera a localidade da aplica&ccedil;&otilde;o, ou seja, se atualmente
	 * est&aacute; sendo utilizada a lingua português, inglês ou espanhol, por
	 * exemplo.
	 * 
	 * @return Retorna a localidade corrente.
	 * @author <a href="mailto:daniel.camargo@ufms.br">Daniel Camargo</a>
	 */
	public static Locale getLocale() {
		return FacesContext.getCurrentInstance().getViewRoot().getLocale();
	}

	/**
	 * Pega a inst&acirc;ncia do managed bean.
	 * 
	 * @param varName
	 *            Nome do managed-bean SEM as marca&ccedil;&otilde;es da
	 *            express&atilde;o EL #{}.
	 * @return Inst&acirc;ncia do managed-bean.
	 */
	public static Object getELVar(String varName) {
		FacesContext ctx = FacesContext.getCurrentInstance();
		ELContext el = ctx.getELContext();
		ValueExpression ve = createVE(varName, Object.class);
		Object o = ve.getValue(el);
		return o;
	}

	/**
	 * Seta o valor de um objeto no managed bean.
	 * 
	 * @param varName
	 *            Nome do managed bean SEM as marca&ccedil;&otilde;es da
	 *            express&otilde;o EL #{}.
	 * @param newValue
	 *            Objeto que ser&aacute; setado no managed-bean
	 * @author <a href="mailto:daniel.camargo@ufms.br">Daniel Camargo</a>
	 */
	public static void setELVar(String varName, Object newValue) {
		FacesContext ctx = FacesContext.getCurrentInstance();
		ELContext el = ctx.getELContext();
		ValueExpression ve = createVE(varName, Object.class);
		ve.setValue(el, newValue);
	}

	/**
	 * M&eacute;todo privado usado para auxiliar os m&eacute;todos de setELVar e
	 * getELVar.
	 * 
	 * @param varName
	 *            Nome do managed bean SEM as marca&ccedil;&otilde;es da
	 *            express&otilde;o EL #{}
	 * @param retorno
	 *            Classe do managed bean
	 * @return Um express&otilde;o EL
	 * @author <a href="mailto:daniel.camargo@ufms.br">Daniel Camargo</a>
	 */
	private static ValueExpression createVE(String varName,
			Class<Object> retorno) {
		FacesContext ctx = FacesContext.getCurrentInstance();
		ExpressionFactory expf = ctx.getApplication().getExpressionFactory();
		ValueExpression ve = expf.createValueExpression(ctx.getELContext(),
				"#{" + varName + "}", retorno);
		return ve;
	}

	/**
	 * Cria uma lista de selectItens onde o value = valor do enum, e o label =
	 * valor I18n do enum.
	 * 
	 * @param type
	 * @param firstItemLabel
	 * @param resolveEnum
	 *            Indica se deve buscar o nome do enum no arquivo de recurso.
	 * @param sort
	 * @return
	 */
	public static List<SelectItem> createSelectItemList(
			Class<? extends Enum> type, String firstItemLabel,
			boolean resolveEnum, boolean sort) {

		List<SelectItem> preResult = new ArrayList<SelectItem>();
		if (firstItemLabel == null) {
			firstItemLabel = "";
		}

		for (Enum e : type.getEnumConstants()) {
			preResult.add(new SelectItem(e, resolveEnum ? MessagesUtils
					.resolveEnum(e) : e.name()));
		}
		if (sort) {
			Collections.sort(preResult, new Comparator<SelectItem>() {
				@Override
				public int compare(SelectItem item1, SelectItem item2) {
					return item1.getLabel().compareTo(item2.getLabel());
				}
			});
		}
		List<SelectItem> result = new ArrayList<SelectItem>();
		result.add(new SelectItem(null, firstItemLabel));
		result.addAll(preResult);
		return result;
	}

	/**
	 * Cria uma lista para um combo
	 * 
	 * @param <T>
	 *            O tipo de Retorno
	 * @param lista
	 *            Os elementos
	 * @param firstItemLabel
	 *            O Label do primeiro item
	 * @param separator
	 *            o separador para quando tiver muitos campos para o label
	 * @param ordena
	 *            - indica se a lista de valores precisa ser ordenada
	 * @param fieldLabel
	 *            - os campos que serviram de label
	 * @return
	 */
	public static <T extends Serializable> List<SelectItem> createSelectItemList(
			List<T> lista, String firstItemLabel, String separator,
			boolean ordena, String... fieldLabel) {
		if (separator == null) {
			separator = "";
		}
		List<SelectItem> retorno = new ArrayList<SelectItem>();
		retorno.add(new SelectItem(null, firstItemLabel == null ? ""
				: firstItemLabel));
		List<SelectItem> listaAuxiliar = new ArrayList<SelectItem>();
		for (T item : lista) {
			StringBuilder labelBuffer = new StringBuilder();
			for (String label : fieldLabel) {
				if (!labelBuffer.toString().isEmpty()) {
					labelBuffer.append(separator);
				}
				try {
					labelBuffer
							.append(BeanUtils.getNestedProperty(item, label));
				} catch (Exception e) {
					throw new SystemException(e);
				}
			}
			listaAuxiliar.add(new SelectItem(item, labelBuffer.toString()));
		}
		if (ordena) {
			sortSelectItemList(listaAuxiliar);
		}
		retorno.addAll(listaAuxiliar);
		return retorno;
	}

	/**
	 * M&eacute;todo para executar a ordena&ccedil;&atilde;o da
	 * cole&ccedil;&atilde;o de items desconsiderando caracteres especiais.
	 * 
	 * @param result
	 *            the result
	 */
	private static void sortSelectItemList(List<SelectItem> result) {
		Collections.sort(result, new Comparator<SelectItem>() {

			@Override
			public int compare(SelectItem o1, SelectItem o2) {
				Collator c = Collator.getInstance(new Locale("pt", "BR"));
				if (c.compare(o1.getLabel(), o2.getLabel()) > 0) {
					return 1;
				} else if (c.compare(o1.getLabel(), o2.getLabel()) < 0) {
					return -1;
				}
				return 0;
			}
		});
	}

	/**
	 * Find managed bean.
	 * 
	 * @param beanName
	 *            the bean name
	 * @param beanClass
	 *            the bean class
	 * @return t
	 */
	public static <T> T findManagedBean(String beanName, Class<T> beanClass) {
		FacesContext faces = FacesContext.getCurrentInstance();
		String expression = "#{" + beanName + "}";
		T managedBean = faces.getApplication().evaluateExpressionGet(faces,
				expression, beanClass);
		return managedBean;
	}

	/**
	 * Verifica se os atributos do objeto do parametro estao nulos ou vazios.
	 * 
	 * @param criteria
	 *            um objeto contendo os filtros da pesquisa
	 * @return true se todos os atributos do objeto estiverem nulos ou vazios.
	 */
	public static boolean isCriteriaEmpty(Object criteria) {
		if (criteria == null) {
			return true;
		}
		String aux = null;
		for (Field field : criteria.getClass().getDeclaredFields()) {
			try {
				aux = BeanUtils.getProperty(criteria, field.getName());
				if (aux != null && !aux.isEmpty()) {
					return false;
				}
			} catch (Exception e) {
				log.info("O campo \"#0\" nao esta no padrao javabean",
						field.getName());
			}
		}
		return true;
	}

	/**
	 * Redireciona o usuario para a pagina alvo.
	 * 
	 * @param page
	 *            alvo
	 */
	public static void goToPage(String page) {
		FacesContext facesContext = FacesContext.getCurrentInstance();
		HttpServletResponse response = (HttpServletResponse) facesContext
				.getExternalContext().getResponse();
		HttpServletRequest request = (HttpServletRequest) facesContext
				.getExternalContext().getRequest();
		String url = request.getContextPath();
		try {
			response.sendRedirect(url + page);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Este m&eacute;todo consegue criar listas constumizadas partindo os
	 * valores do enum.
	 * 
	 * @param type
	 *            o tipo de enum
	 * @param firstItemLabel
	 *            o label da primeira op&ccedil;&otilde;o do selec
	 * @param sort
	 *            define se a lista deve ou n&otilde;o estar ordenada
	 * @param values
	 *            se n&otilde;o nulo apenas os valores deste array ser&otilde;o
	 *            considerados.
	 * @return uma lista de elementos do tipo type
	 */
	public static <T extends Enum<?>> List<SelectItem> getPartialEnumValuesSelectItems(
			Class<T> type, String firstItemLabel, boolean sort, T... values) {
		ArrayList<SelectItem> result = new ArrayList<SelectItem>();
		if (firstItemLabel != null && !firstItemLabel.isEmpty()) {
			result.add(new SelectItem("", firstItemLabel));
		}
		List<T> list = null;
		if (values != null) {
			list = Arrays.asList(values);
		}
		for (T e : type.getEnumConstants()) {
			boolean insert = true;
			if (list != null) {
				insert = list.contains(e);
			}
			if (insert) {
				result.add(new SelectItem(e, MessagesUtils
						.getMessageInApplicationBundle(type.getSimpleName()
								+ "." + e.name())));
			}
		}
		return result;
	}

	/**
	 * Response.
	 */
	public static void response() {
		getFacesContext().responseComplete();
	}

	/**
	 * Enviar o arquivo para o response.
	 * 
	 * @param dados
	 *            : o byte[] contendo o arquivo
	 * @param fileName
	 *            : o nome do arquivo
	 * @param isAttachment
	 *            : define se vai ser inline ou attachment
	 * @param contentType
	 *            the content type
	 * @throws Exception
	 *             the exception
	 */
	public static void sendOutputFile(byte[] dados, String fileName,
			String contentType, boolean isAttachment) throws Exception {
		FacesContext facesContext = FacesContext.getCurrentInstance();
		HttpServletResponse response = (HttpServletResponse) facesContext
				.getExternalContext().getResponse();
		String outputType = (isAttachment) ? "attachment" : "inline";
		response.setHeader("Content-Disposition", outputType + ";filename=\""
				+ fileName + "\"");
		response.setContentLength(dados.length);
		response.setContentType(contentType);
		// avisa o cliente via cookie que o download esta disponivel
		// jQuery.cookie("fileDownloadToken", null);
		Cookie cookie = new Cookie("fileDownloadToken", "1");
		response.addCookie(cookie);
		OutputStream out = response.getOutputStream();
		out.write(dados);
		out.flush();
		out.close();
		facesContext.responseComplete();
	}

	/**
	 * Send output file.
	 * 
	 * @param file
	 *            the file
	 * @param contentType
	 *            the content type
	 * @param isAttachment
	 *            the is attachment
	 * @throws Exception
	 *             the exception
	 */
	public static void sendOutputFile(File file, String contentType,
			boolean isAttachment) throws Exception {
		byte[] dados = getBytesFromFile(file);
		String path = file.getAbsolutePath();
		String fileName = path.substring(
				path.lastIndexOf(System.getProperty("file.separator")),
				path.length());
		sendOutputFile(dados, fileName, contentType, isAttachment);
	}

	/**
	 * M&ecute;todo de recupera&ccedil;&atilde;o do campo bytes from file.
	 * 
	 * @param file
	 *            the file
	 * @return o campo bytes from file
	 * @throws IOException
	 *             the IO exception
	 */
	private static byte[] getBytesFromFile(File file) throws IOException {
		InputStream is = new FileInputStream(file);

		// Get the size of the file
		long length = file.length();

		// Create the byte array to hold the data
		byte[] bytes = new byte[(int) length];

		// Read in the bytes
		int offset = 0;
		int numRead = 0;
		while (offset < bytes.length
				&& (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
			offset += numRead;
		}

		if (offset < bytes.length) {
			throw new IOException("Could not completely read file "
					+ file.getName());
		}

		is.close();
		return bytes;
	}
}
